import java.util.ArrayList;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.Properties;
import java.util.logging.Handler;
import java.util.logging.SimpleFormatter;
import java.util.logging.FileHandler;
import java.io.FileNotFoundException;
import java.io.FileInputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * This class is used to start the Webster HTTP server
 * or the Webster HTTPD, depending on the parameters passed
 * through from the run shellscript.
 *
 * @author Marlon Etheredge
 * @author <a href="mailto:m.etheredge@gmail.com">m.etheredge@gmail.com</a>
 * @author <a href="http://www.foxage.nl/">http://www.foxage.nl</a>
 */
public class Webster {
	
	/** Name of the properties file. */
	private static final String propertiesfileName = "Webster.properties";
	
	/** Properties instance loaded from properties file specified by propertiesfileName. */
	private static Properties properties;
	
	/** Logger instance used to log messages to the Webster logfile. */
	private static Logger logger;
	
	/** Webserver Array containing all Webserver instances of Webster. */
	private static ArrayList<Webserver> webservers = new ArrayList<Webserver>();
	
	/** Value of the HTTP server header field. */
	private static final String httpServer = "webster/0.0.1";
	
	/** Logger Handler for the GUI. */
	private static Handler loggerHandler;
	
	/**
	 * Wether to run Webster in verbose mode, this variable is public because
	 * declaring a getter for this variable could cause a serious lack of performance.
	 */
	public static boolean verbose;

	/**
	 * Returns the Webservers.
	 *
	 * @return ArrayList<Webserver> The Webservers.
	 */
	public static ArrayList<Webserver> getWebservers() {
		return webservers;
	}

	/**
	 * Sole entry point to the class and application.
	 *
	 * @param args Array of String objects passed through from
	 *	       the run shellscript, containing all run parameters
	 */
	public static void main(String[] args) {
		initializeWebster();
	}
	
	/**
	 * Initializes Webster.
	 */
	public static void initializeWebster() {
		initializeProperties();
		initializeLogger();
		
		verbose = (getProperty("verbose").equals("false") ? false : true);
		
		//Write an initial logmessage to the logfile
		log(Level.INFO, "Webster running and initialized");
		
		createWebserver();
	}
	
	/** 
	 * Returns the httpServer.
	 *
	 * @return The httpServer field
	 */
	public static String getHttpServer() {
		return httpServer;
	}
	
	/**
	 * Initializes the properties, needed to run Webster,
	 * in case of an exception thrown, exit will be called
	 * since Webster cannot run without it's properties loaded.
	 */
	private static void initializeProperties() {
		//Intiantiate properties variable with a new Properties instance
		properties = new Properties();
		
		FileInputStream fileInputStream = null;
		
		try {
			fileInputStream = new FileInputStream(propertiesfileName);
		} catch(FileNotFoundException exception) {
			System.out.println("Exception thrown at Webster.initializeProperties(), " +
					   "Properties file could not be found, Webster cannot continue " +
					   "running, and will now exit.");
							
			System.exit(1);
		}
		
		try {
			properties.load(fileInputStream);
		} catch (Exception exception) {
			System.out.println("Exception thrown at Webster.initializeProperties(), " +
					   "Webster cannot continue running, and will now exit. " + exception);
							
			System.exit(1);
		}
	}
	
	/**
	 * Sets the loggerHandler.
	 *
	 * @param handler The loggerHandler.
	 */
	public static void setLoggerHandler(Handler handler) {
		loggerHandler = handler;
	}
	
	/**
	 * Initializes the logger for Webster, needed to log all actions
	 * performed, in case of an exception thrown, exit will be called,
	 * since this is a vital part of Webster, and Webster will not run
	 * without logging.
	 */
	private static void initializeLogger() {
		//Intiantiate logger variable with a new Logger instance named 'Webster'
		logger = Logger.getLogger("Webster");
		
		if(loggerHandler != null)
			logger.addHandler(loggerHandler);
		
		SimpleFormatter formatter = new SimpleFormatter();
		
		FileHandler fileHandler = null;
		
		try {
		    fileHandler = new FileHandler(getProperty("logpath") + "/webster.log");
		} catch(Exception exception) {
			System.out.println("Exception thrown at Webster.initializeLogger(), " +
					   "Webster cannot continue running, and will now exit. " + exception);
							
			System.exit(1);
		}
		
		logger.addHandler(fileHandler);
		logger.setLevel(Level.ALL);
		fileHandler.setFormatter(formatter);
	}
	
	/** 
	 * Logs a message to the Webster logfile, with a specified Level and
	 * a specified message.
	 *
	 * @param level Level instance with the Level (severity) of message to log
	 * @param message String object containing the message to log to the logfile
	 */
	public static void log(Level level, String message) {
		logger.log(level, message);
	}
	
	/** Calls getProperty on Webster properties instance. */
	public static String getProperty(String propertyName) {
		return properties.getProperty(propertyName);
	}
	
	/** 
	 * Creates a new Webserver instance with specified port and 
	 * adds it to the webservers ArrayList .
	 *
	 * @param port Portnumber for the new Webserver
	 */
	public static void createWebserver(InetAddress bindAddress, int port) {
		Webserver webserver = new Webserver(bindAddress, port);
		webservers.add(webserver);
	}
	
	/** Creates a new Webserver instance with default port and adds it to the webservers ArrayList. */
	public static void createWebserver() {
		Webserver webserver = new Webserver(getAllLocalBindAddresses()[0], 80);
		webservers.add(webserver);
	}
	
	/** 
	 * Remove specified  Webserver from the webserver
	 * ArrayList, and stop it's Thread.
	 *
	 * @param webserver Webserver to remove
	 */
	public static void removeWebserver(Webserver webserver) {
		webserver.stopListen();
		webservers.remove(webserver);
		
		if(verbose)
			Webster.log(Level.INFO, "Removed Webserver \"" + webserver + "\"");
	}
	
	/** Remove last Webserver from the webserver ArrayList. */
	public static void removeWebserver() {
		Webserver webserver = webservers.get((webservers.size() - 1));
		webserver.stopListen();
		webservers.remove(webserver);
	}

	/**
	 * Returns an array containing all InetAddresses for this host.
	 *
	 * @return IntenAddress[] All InetAddresses
	 */
	public static InetAddress[] getAllLocalBindAddresses() {
		InetAddress[] addresses = null;	

		try {
			addresses = InetAddress.getAllByName(InetAddress.getLocalHost().getHostName());
		} catch(UnknownHostException exception) {
			log(Level.SEVERE, "Host is unknown, this is a serious error, and thus Webster functionality will be limited.");
		}

		return addresses;
	}

	/**
	 * Finalize method, in this case cleans up the Socket.
	 */
	protected void finalize() throws Throwable {
		for(Webserver webserver : getWebservers()) {
			removeWebserver(webserver);
		}
	}
	
}
